feat: propagate structured CipherStash auth refusals - #894
Conversation
🦋 Changeset detectedLatest commit: d2a8f08 The changes in this PR will be included in the next version bump. This PR includes changesets to release 20 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
coderdan
left a comment
There was a problem hiding this comment.
To be honest, I think this PR is the wrong approach. It's doing too much heavy lifting. The error type in stack_auth already implements miette::Diagnostic, So I think we can just add #[diagnostic(transparent)] to the AuthError enum variant on the protect_ffi Error. The stack/protect-ffi layer should be really thin.
StackAuth should include a code, message and instructions on the diagnostic for when CTS returns a 402. Then any consumer of stack_auth, including Proxy would get the same response.
a76bb7e to
82b3ad1
Compare
|
Force-pushed a history rewrite, and it changed more than commit hashes — flagging it since the diff itself did not move. Four commits became three, split by layer rather than by chronology: What went away was churn, not content: a commit that guessed at the wire format and a later one that corrected it, a remedy map written to override upstream and a later flip to prefer upstream, two rounds of review fixes, and a formatting commit forced by a Biome bump on main. The rewrite is content-neutral — the tree hash is byte-identical before and after ( The title and body are rewritten too. The old ones described the pre-0.42.3 design — they claimed the dashboard link was folded into |
d61f40f to
6bd1bb5
Compare
|
The One thing to pick up separately, not a blocker. The two direct-HTTP CTS paths can't reach the diagnostics this PR adds.
What survives the deprecation is the asymmetry:
Worth flagging before anyone reaches for the obvious fix: Options, roughly in my order of preference:
Happy for this to land as-is and for that to be a follow-up. |
freshtonic
left a comment
There was a problem hiding this comment.
Requesting changes. The engineering is sound — the propagation path is correct where it is live, the Result contract and EncryptionErrorTypes strings are untouched, the test work is unusually rigorous, and CI is fully green including the credentialed live suite. What blocks is prose that ships to customers stating as current behaviour something the pinned dependency provably cannot do yet, plus a pair of docblocks describing a mechanism this PR's own rework deleted. All fixes are text-level.
1. BLOCKING — the CLI half is dormant, but the skills and changeset say it's live
The taxonomy (USAGE_LIMIT_EXCEEDED, ORG_NOT_PROVISIONED, cs_code) ships in stack-auth 0.42.3, published to crates.io 2026-08-26. The npm @cipherstash/auth this PR pins is 0.43.0, published 2026-08-12 — the latest available, and structurally two weeks too old to contain it. I verified both registry timestamps directly, and a byte-search of the published 0.43.0 binaries (darwin-arm64 .node and the wasm build) finds zero occurrences of the taxonomy codes or cs_code, with NOT_AUTHENTICATED present as the positive control; the 0.43.0 AuthFailure union in index.d.ts lacks both types too.
So today, a real CTS 402 during stash env renewal or stash auth login arrives from the auth binding as a pre-taxonomy type, falls through to session_invalid + the login hint — the exact wrong-advice loop this PR exists to kill. Every CLI test mocks @cipherstash/auth, so the suites pin the rendering, not the live path. (The native @cipherstash/stack entry, LockContext.identify(), and @cipherstash/nextjs are live — protect-ffi embeds stack-auth 0.42.3, and the identify/nextjs paths parse the 402 themselves.)
The tests already know this — packages/cli/src/commands/auth/__tests__/failure.test.ts says the lookups "start returning the terminal answer the day the dependency ships the code". The customer-facing text doesn't:
skills/stash-cli/SKILL.md: "That case reportsusage_limit_exceeded…", "fails withUSAGE_LIMIT_EXCEEDEDon the--jsonstream"skills/stash-auth/SKILL.md: "On the CLI,stash envreports these under their own codes…".changeset/usage-limit-refusal-guidance.md: "stash auth loginandstash envnow consume@cipherstash/auth0.43.0's typed failures"
Either gate the PR on an auth release carrying the taxonomy and bump the pin, or reword these three to conditional/future phrasing. Skills land in customer repos; per AGENTS.md a wrong sentence there is a wrong line of code.
2. Dangling AuthDiagnosticRelay + false "verbatim relay" claim in shipped TSDoc
packages/protect-ffi/src/types.tsandsrc/index.types.test.tsboth say "seeAuthDiagnosticRelayincrates/protect-ffi/src/lib.rs" — no such item exists; it's a leftover from the pre-rework design (the rework moved to#[diagnostic(transparent)]and deleted the relay).packages/protect-ffi/src/errors.ts(and the trackeddist/wasm/errors.d.ts) claim aconfig.authStrategyfailure'shelp/url/typeare relayed "verbatim — not values re-derived from the stack-auth enum". The implementation does the opposite:neon_failure_to_auth_errordrops the reserved keys and reconstructs viaAuthError::from_error_code, whose help/url come from the enum's fixed#[diagnostic]attributes, and unknown codes collapse toCUSTOM. A strategy's own remedy text never survives. The type-level test only pins that the type accepts the fields, as its own docblock admits.
3. Minor text fixes
packages/cli/src/commands/auth/failure.ts: the comment claiming byte-identity withTERMINAL_PUNCTUATIONinpackages/stack/src/encryption/helpers/auth-failure.ts— that file contains no such constant and no message-joining; no guard compares them. Rework leftover.- Test narration in
identify-cts-refusal.test.ts(and the nextjs twin) says the two legacy codes are "read fromerror"; neither classifier readserrorat all — absentcs_codedefaults unconditionally. Behaviour correct, narration wrong. - Worth one sentence somewhere:
auth login --jsonemitsUSAGE_LIMIT_EXCEEDEDwhileenv --jsonemitsusage_limit_exceeded— each consistent with its command's convention, but an agent consuming both streams matches two spellings of one condition.
Verified clean (no need to re-argue these)
Result contract and error-type strings untouched, all new fields optional/additive across every error shape including bulk per-row items; #[diagnostic(transparent)] cannot leak a foreign code into ProtectErrorCode (checked every crate in the chain for #[diagnostic(code(...))] — none); all 20 operation mappers converted and parameter-tested, with mocks made partial precisely so the real isProtectErrorCode runs; the wasm-inline readErrorCode change closes a genuine accepts-any-string type lie; no native specifier leaks into the wasm bundle; skills touched match the AGENTS.md map and nothing in the untouched skills became false; four changesets present and correctly leveled (modulo the finding-1 sentence); Rust test literals match stack-auth 0.42.3's attributes verbatim; no plaintext/secret logging; CI green including cargo checks and the live integration suite. Also worth saying out loud since the body undersells it: packages/nextjs had no test script at all — its suite was dead until this PR wired it up and repaired it. Good change.
Happy to re-review as soon as the finding-1 wording (or the pin) and the finding-2 docblocks are settled.
6bd1bb5 to
7018cca
Compare
068196f to
d2a8f08
Compare
freshtonic
left a comment
There was a problem hiding this comment.
Approving at d2a8f08. Every finding from my previous review is resolved, and I re-verified the load-bearing one against the registry rather than the prose:
- The dormancy is gone — the pin now points at a binding that really carries the taxonomy.
@cipherstash/auth0.44.0 (published today, 03:43Z) is pinned across all seven catalog names together, per the supply-chain rule. I unpacked the publishedauth-darwin-arm64@0.44.0binary and foundUSAGE_LIMIT_EXCEEDED,ORG_NOT_PROVISIONED, andcs_codepresent (withNOT_AUTHENTICATEDas the positive control), and 0.44.0'sindex.d.tsdeclares both new members on theAuthFailureunion. The changeset and skills' present-tense claims ("now consume 0.44.0's typed failures") are now true statements about the shipping dependency, and CI's credentialed integration suite is green on the bump. - The "verbatim relay" claim is rewritten to match the implementation (reconstruction from
type+ message + variant payload; known codes get the variant's guidance, unknown collapse toCUSTOM, callerhelp/urlnot forwarded) — inerrors.ts,types.ts, the type test, and the trackeddist/wasm/errors.d.ts. Both danglingAuthDiagnosticRelayreferences are gone. - The
TERMINAL_PUNCTUATIONbyte-identity comment is replaced with the truthful local-presentation note, and the "reads offerror" test narrations now say what the classifiers actually do (default to the usage limit). - The casing split even got documented —
skills/stash-clinow tells agentsUSAGE_LIMIT_EXCEEDED(login) andusage_limit_exceeded(env) are one condition. That was my optional item; nice to see it land.
The nextjs boundary rationale (don't pull platform binaries into middleware for a string lookup) is also better argued than before. Good rework all round.
Summary
Propagate structured CipherStash authentication refusals through protect-ffi, Stack, Next.js, and the
stashCLI.stack-authremains the source of truth for diagnostic codes, messages, instructions, and links. Downstream layers preserve those diagnostics rather than rebuilding or duplicating guidance.Changes
miettediagnostics and expose theircode,authCode,help, andurlfields across native and WASM bindings, including bulk item failures.@cipherstash/stackoperations and client initialization.402responses consistently inLockContext.identify()and@cipherstash/nextjs: known or legacy responses become typed refusals, while unknowncs_codevalues are left unclassified.@cipherstash/authto 0.44.0 and render its guidance instash auth loginandstash env, including machine-readable terminal refusal codes.Verification
git diff --checkThe full Stack suite's live-service cases require CipherStash credentials. The CLI declaration build also requires the generated
@cipherstash/eql/sqlartifact.Related
Refs cipherstash/cipherstash-suite#2120